震惊,这段代码居然有 4 种执行结果

发表于 2023-05-05 04:14:39
更新于 2024-04-18 13:33:37
Javascript

题目

javascript
var a = 0;
if (true) {
    a = 1;
    function a() {return 3}
    a = 2;
    console.log(a);
}
console.log(a);

问题是问两个打印机处的 a 会是什么?

直觉上我们可能会答:2 和 2,可是在我们熟悉的 chrome 中,结果居然是 2 和 1。

其实还有人说过,在 safari 中,结果是 2 和 2。没想到,最懂我的还是 safari。

我自己额外还发现了两种不同情况,如果加上严格模式,结果是 2 和 0;在带有编译环境的情况下,执行结果会是 2 和 ƒ () { return 3; }

这段代码的问题

这段代码既然我们可能怎么答都不可能答对,不如贬低一下它。

  • 不应该使用 var
  • 不应该把函数声明为和原本变量名一样
  • 不应该在一个条件里,使用 function 去声明函数(应该使用函数表达式)

关于这里的第三点,我们可以从 MDN 上找到说法: “这种声明方式在不同的浏览器里可能有不同的效果”。

我们也就可以稍稍理解为什么 chrome 和 safari 的不同结果了。

调试技巧

其实我如果我们真的想弄明白执行结果,可以采用单步执行查看调用时产生的作用域变量就可以。

针对有编译环境的代码,我们可以直接查看其编译结果,这个相对容易,我们先说这一点。

编译环境:输出 2 和 function

我使用的环境是 vite,只引入了我们题目的代码,编译结果如下:

javascript
var a = 0;
if (true) {
  let a2 = function() {
    return 3;
  };
  var a = a2; // 这里会导致最终 a 是个 函数
  a2 = 1;
  a2 = 2;
  console.log(a2);
}
console.log(a);

我们发现由于编译的介入,函数已经被转换成了表达式,而且命名也被替换了,这一没有必要单步执行,按照一行一行执行,结果是 2 和 function 的打印。

safari 下单步调试

我们直接在控制台下输入:

javascript
debugger
var a = 0;
if (true) {
    a = 1;
    function a() {return 3}
    a = 2;
    console.log(a);
}
console.log(a);

safari 做了函数的提升,最早 a 是一个 function。

执行完 var a = 0, a 变成 0。

后续两次打印是 a 都是 2。

chrome 非严格模式

我们把同样的代码粘贴到 chrome 控制台运行。

刚进入 if 时,global 下的 a 是 0,block 下的 a 是 function。

执行到第一次输出时,global 下的 a 是 1,block 下的 a 是 2。这里不知道什么时候全局下的 a 变成 1 的,深究下去也没有意义,这也正是不符合我们直觉的地方。

所以最后的输出是 2 和 1。

chrome 严格模式 (safari 同样的执行结果,只看一下 chrome)

同样的代码,我们只加上严格模式:

javascript
'use strict';
debugger;
var a = 0;
if (true) {
    a = 1;
    function a() {return 3}
    a = 2;
    console.log(a);
}
console.log(a);

同样的方式,查看调用栈,严格模式下,global 下的 a 不会莫名地变成 1,依然保持 0。

所以输出 2 和 0。

总结

纠结输出什么其实没有意义,这道题在我的实验环境下就有 4 中输出。

给我们的唯一作用就是警示我们写出符合规范的代码。